让我们首先看一个“hello world”程序,其中包含从数学库调用函数的示例。在表1 中我们可以对比该程序由C语言和python分别的实现。 C语言版本放在了hello.c 的文件中(.c 是 C 源代码文件的后缀约定),而python版本放在了hello.py文件中。

Python 和 C 语言小程序的语法比较

'''
    The Hello World Program in Python
'''

# Python math library
from math import *


# main function definition:
def main():
    # statements on their own line
    print("Hello World")
    print("sqrt(4) is %f" % (sqrt(4)))

# call the main function:
main()
/*
    The Hello World Program in C
 */

/* C math and I/O libraries */
#include <math.h>
#include <stdio.h>

/* main function definition: */
int main(void) {
    // statements end in a semicolon (;)
    printf("Hello World\n");
    printf("sqrt(4) is %f\n", sqrt(4));

    return 0;  // main returns value 0
}

这个示例程序的两个版本有相似的结构和语言结构,虽然语法上不尽相同。表现在以下几个方面: 注释:

  • 在Python中,多行注释以'''开头和结尾,单行注释以#开头。
  • 在C语言中,多行注释以/*开头,以 */结尾,单行注释以 // 开头。

导入代码库:

  • 在 Python 中,使用 import 来包含(导入)库。
  • 在 C 语言中,使用 #include 包含(导入)库。所有#include 语句都出现在程序顶部、位于函数体之外。

代码块:

  • 在Python中,缩进表示一个代码块。
  • 在C语言中,代码块(例如函数、循环和条件体)以 { 开头,以}结尾。

main函数:

  • 在Python中,def main():定义main函数。
  • 在C语言中,int main(void){ }定义了main函数。main函数返回一个int类型的值,这是C语言中用于指定有符号整数类型的名称(有符号整数是 -3、0、1234 等值)。main函数返回int值0表示运行完成且没有错误。void表示它不希望接收参数。后续部分将展示 main如何使用参数来接收命令行参数

语句:

  • 在 Python 中,每个语句都位于单独的行上。
  • 在 C 语言中,每个语句都以分号;结尾。在 C语言中,语句必须位于某个函数的主体内(在本示例中位于main中)。

输出:

  • 在Python中,print函数打印格式化字符串。格式化字符串中的%占位符所表示的值位于以逗号分隔的值列表中(例如,在打印输出时sqrt(4) 的值会替代格式化字符串中的 %f占位符)。
  • 在 C 语言中,printf函数打印格式化字符串。格式字符串中占位符的值是用逗号分隔的额外参数((例如,在打印输出时sqrt(4) 的值会替代格式化字符串中的 %f占位符)

这个程序的 C 语言版本和 Python 版本有一些重要的区别需要注意: 缩进:在 C 语言中,缩进没有意义,但根据包含块的嵌套级别来缩进语句是一种很好的编程风格。 main 函数:

  • C程序必须有一个名为 main 的函数,并且其返回类型必须为 int。这意味着main函数返回了一个有符号的整数类型值。 Python 程序不需要将其主函数命名为 main,但它们通常按照约定命名。
  • C语言的main函数有一个显式的return语句来返回一个int值(按照惯例,如果main函数成功执行且没有错误,应该返回 0)。
  • Python 程序需要显式调用main函数,以便在程序执行时运行它。在C程序中,它的main函数在C程序执行时自动被调用。

1.1.1 编译和运行 C 程序

Python是一种解释性编程语言,这意味着Python程序是由另一个程序即Python解释器运行的:Python解释器的作用就像一个运行Python程序的虚拟机。要运行 Python 程序,程序源代码 (hello.py) 将作为运行该程序的 Python 解释器程序的输入。例如:

$ python hello.py

Python解释器是可以直接在底层系统上运行的一种程序(这种形式称为二进制可执行文件),并将其运行的Python程序作为输入(图1)。 图1.Python程序由Python解释器直接执行,是运行在底层系统(操作系统和硬件)上的二进制可执行程序

要运行C程序,首先必须将其翻译成计算机系统可以直接执行的形式。C编译器就是用来将C源代码翻译成计算机硬件可以直接执行的二进制可执行形式的程序。二进制可执行文件由一系列 0 和 1 组成,采用计算机可以运行的明确定义的格式。 例如,要在 Unix 系统上运行 C 程序 hello.c,C代码必须首先由 C编译器(例如 GNU C 编译器 GCC)编译,生成二进制可执行文件(默认名称为 a.out)。然后可以直接在系统上运行该程序的二进制可执行版本(图2)。

$ gcc hello.c
$ ./a.out

(请注意,某些 C 编译器可能需要明确告知要链接到数学库:-lm):

$ gcc hello.c -lm

图2.C 编译器 (gcc) 将 C 源代码构建为二进制可执行文件 (a.out)。底层系统(操作系统和硬件)直接执行a.out文件来运行程序。

详细步骤

一般来说,以下顺序描述了在 Unix 系统上编辑、编译和运行 C 程序的必要步骤:

  1. 使用文本编辑器(例如 vim),编写 C 源代码程序并将其保存在文件中(例如 hello.c):
$ vim hello.c
  1. 将源代码编译为可执行形式,然后运行它。使用 gcc 编译的最基本语法:
$ gcc <input_source_file>

如果编译没有产生错误,编译器将创建一个名为 a.out 的二进制可执行文件。编译器还允许您使用 -o 来指定要生成的二进制可执行文件的名称:

$ gcc -o <output_executable_file> <input_source_file>

例如,下面的命令指示 gcc 将 hello.c 编译为名为 hello 的可执行文件:

$ gcc -o hello hello.c

我们可以使用 ./hello 调用可执行程序

$ ./hello

对 C 源代码(hello.c 文件)所做的任何更改都必须使用 gcc 重新编译以生成新版本的 hello。如果编译器在编译过程中检测到任何错误,则不会创建/重新创建 ./hello 文件(但请注意,先前成功编译的旧版本文件仍然存在)。 通常,在使用 gcc 编译时,您想要包含多个命令行选项。例如,开启更多编译器警告并构建带有额外调试信息的二进制可执行文件的选项:

$ gcc -Wall -g -o hello hello.c

由于 gcc 命令行可能很长,因此经常使用 make程序来简化编译 C 程序和清理 gcc 创建的文件。使用 make和编写 Makefile是你在积累 C 编程经验时需要培养的重要技能。 我们将在第 2 章末尾更详细地介绍 C 库代码的编译和链接。

1.1.2 C 变量和 C 数字类型

与 Python 一样,C 使用变量作为保存数据的命名空间。考虑程序变量的范围与类型对于理解程序运行时将执行的操作的语义非常重要。变量的作用域定义了变量何时有意义(即,在程序中何时、何地可以使用它)及其生命周期(即,它可以在程序的整个运行过程中持续存在,或者仅在函数激活期间持续存在)。变量的类型定义了它可以表示的值的范围以及在对其数据执行操作时如何解释这些值。

在C语言中,所有变量都必须先声明才能使用。声明变量的语法如下:

type_name variable_name;

一个变量只能有单一类型。基本的 C 类型包括 char、int、float 和 double。按照约定,C变量应在其作用域的开头(在 { } 块的顶部)声明,位于该作用域中的任何C语句之前。

下面是一个示例 C 代码片段,显示了一些不同类型的变量的声明和使用。我们在示例之后更详细地讨论类型和运算符。

{
    /* 1. Define variables in this block's scope at the top of the block. */

    int x; // declares x to be an int type variable and allocates space for it

    int i, j, k;  // can define multiple variables of the same type like this

    char letter;  // a char stores a single-byte integer value
                  // it is often used to store a single ASCII character
                  // value (the ASCII numeric encoding of a character)
                  // a char in C is a different type than a string in C

    float winpct; // winpct is declared to be a float type
    double pi;    // the double type is more precise than float

    /* 2. After defining all variables, you can use them in C statements. */

    x = 7;        // x stores 7 (initialize variables before using their value)
    k = x + 2;    // use x's value in an expression

    letter = 'A';        // a single quote is used for single character value
    letter = letter + 1; // letter stores 'B' (ASCII value one more than 'A')

    pi = 3.1415926;

    winpct = 11 / 2.0; // winpct gets 5.5, winpct is a float type
    j = 11 / 2;        // j gets 5: int division truncates after the decimal
    x = k % 2;         // % is C's mod operator, so x gets 9 mod 2 (1)
}

请注意大量的分号。回想一下,C 语句是用;来划分的,而不是换行符 ,C 语言每个语句都以分号结尾。你可能会遗漏一些分号,而且gcc几乎不会通知你遗漏了分号,即使这可能是程序中唯一的语法错误。事实上,通常当你忘记分号时,编译器会在缺少分号的行后面指示语法错误:原因是gcc将其解释为上一行语句的一部分。当你继续使用 C 进行编程时,将学习把 gcc 报错与其描述的特定 C 语法错误相关联。

1.1.3 C 类型

C语言内置了一系列数据类型,并且提供了少量可供编程人员可以构造基本类型集合(数组和结构)的方法。从这些基本构建块中,C 程序员可以构建复杂的数据结构。

C 定义了一系列用于存储数值的基本类型。以下是不同 C 类型的数值的一些示例:

8     // the int value 8
3.4   // the double value 3.4
'h'   // the char value 'h' (its value is 104, the ASCII value of h)

C char类型可存储数值。通常,程序员经常使用它来存储 ASCII 字符的对应的数值。在 C 语言中,字符文字值被指定为单引号之间的单个字符。

C语言不支持字符串类型,但是程序员可以从char类型和C语言对数组的构造支持中创建字符串,这一点我们在后面的部分中讨论。然而,C语言确实支持在程序中表达字符串字值的方式:字符串字值是双引号之间的任何字符序列。C 程序员经常将字符串文字作为格式字符串的参数传递给 printf函数:

'h' // this is a char literal value (its value is 104, the ASCII value of h) 
"h" // this is a string literal value (its value is NOT 104, it is not a char)

我们将在本章后面的字符串部分更详细地讨论 C 字符串和 char 变量。在这里,我们将主要关注 C 的数字类型。

c 数字类型

C 语言支持多种不同类型来存储数值。这些类型的不同之处在于它们所表示的数值的格式。例如,float 和 double 类型可以表示实数值,int 表示有符号整数值,unsigned int 表示无符号整数值。实数值是带小数点的正值或负值,例如-1.23 或0.0056。符号整数存储正、负或零整数值,例如 -333、0 或 3456。无符号整数存储非负整数值,例如 0 或 1234。

C 的数字类型在它们可以表示的值的范围和精度方面也有所不同。值的范围或精度取决于与其类型相关联的字节数。与具有较少字节的类型相比,具有更多字节的类型可以表示更大范围的值(对于整数类型)或更高精度的值(对于实型类型)。

Type nameUsual sizeValues storedHow to declare
char1 byteintegerschar x;
short2 bytessigned integersshort x;
int4 bytessigned integersint x;
long4 bytes or 8 bytessigned integerslong x;
long long8 bytessigned integerslong long x;
float4 bytessigned real numbersfloat x;
double8 bytessigned real numbersdouble x;

C 语言还提供整数数字类型(char、short、int、long 和 long long)的无符号版本。要将变量声明为无符号类型,在类型名称前添加关键字 unsigned即可。例如:

int x; // x is a signed int variable 
unsigned int y; // y is an unsigned int variable

C 标准没有指定 char 类型是有符号的还是无符号的。因此,某些实现可能将 char 实现为有符号整数值,而其他实现则可能实现为无符号整数值。如果想使用 char 变量的无符号版本,显式声明 unsigned char 是一种很好的编程习惯。

每种 C 类型的确切字节数可能因架构而异。表 2 中的字节数是每种类型的最小(也是常见)字节数。您可以使用 C 的 sizeof 运算符输出给定机器上的确切字节数,该运算符将类型名称作为参数,并计算出用于存储该类型的字节数。例如:

printf("number of bytes in an int: %lu\n", sizeof(int)); 
printf("number of bytes in a short: %lu\n", sizeof(short));

sizeof 运算符的计算结果为无符号长整型值,因此在调用 printf 时,使用占位符 %lu 来打印其值。在大多数架构上,这些语句的输出将是:

number of bytes in an int: 4
number of bytes in a short: 2

算术运算符

算术运算符用于组合数值类型。运算结果的类型取决于被运算的类型。例如,如果两个 int 值与一个算术运算符组合,则结果类型也是一个integer。

当运算符组合两种不同类型的数字类型时,C 会执行自动类型转换。例如,如果 int 类型与 float 类型组合运算,则在应用运算符之前,首先将int类型转换为其等效的float类型,并且运算结果的类型为 float。

以下算术运算符可用于大多数数值类型操作数:

  • 加法(+)和减法(-)

  • 乘法(*),除法(/)和取余(%) mod 运算符 (%) 只能采用整数类型操作数(int、unsigned int、short 等)。

    如果两个操作数都是 int 类型,则除法运算符 (/) 执行整数除法(结果值为 int,截去除法运算中小数点以外的任何内容)。例如,8/3 的计算结果为 2。

    如果一个或两个操作数都是float(或double),则 / 执行实数除法并计算出float(或double)结果。例如,8 / 3.0 的计算结果约为 2.666667。

  • 赋值(=)

	variable = value of expression;  // e.g., x = 3 + 4;
  • 更新赋值(+=-=*=/=, and %=)
	variable op= expression;  // e.g., x += 3; is shorthand for x = x + 3;
  • 递增 (++) 和递减(--)
	variable++;  // e.g., x++; assigns to x the value of x + 1

warning

前增量与后增量 运算符++variable 和variable++ 都是有效的,但它们的计算方式略有不同:

  • ++x:先递增x,然后使用它的值。
  • x++:先使用x的值,然后递增它。

在许多情况下,使用哪一个并不重要,因为递增或递减变量的值并未在语句中使用。例如,这两个语句是等效的(尽管第一个是该语句最常用的语法): x++; y++;

在某些情况下,上下文会影响结果(当在语句中使用递增或递减变量的值时)。例如: x = 6; y = ++x + 2; // y is assigned 9: increment x first, then evaluate x + 2 (9) x = 6; y = x++ + 2; // y is assigned 8: evaluate x + 2 first (8), then increment x 像前面的示例一样,使用带有增量运算符的算术表达式的代码通常难以阅读,而且很容易出错。因此,通常最好避免编写这样的代码;相反,请按照您想要的顺序编写单独的语句。例如,如果您想先递增 x,然后将 x + 1 赋给 y,只需将其写为两个单独的语句即可。 替换下面的写法: y = ++x + 1; 将其换成2行: x++; y = x + 1